/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2020 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function() {

"use strict";

var ui = angular.module("axelor.ui");

ui.ManyToOneCtrl = ManyToOneCtrl;
ui.ManyToOneCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];

function ManyToOneCtrl($scope, $element, DataSource, ViewService) {

  ui.RefFieldCtrl.apply(this, arguments);

  $scope.selectEnable = true;
  var ds = $scope._dataSource;

  $scope.createNestedEditor = function() {

    var embedded = $('<div ui-nested-editor></div>')
      .attr('ng-model', $element.attr('ng-model'))
      .attr('name', $element.attr('name'))
      .attr('x-title', $element.attr('x-title'))
      .attr('x-path', $element.attr('x-path'));

    embedded = ViewService.compile(embedded)($scope);
    embedded.hide();

    var cell, row;

    // if panel form
    if ($element.parent().is("div.form-item")) {
      cell = $("<div></div>").addClass("span12 form-item").append(embedded);
      row = $("<div></div>").addClass("row-fluid").append(cell);
      row.insertAfter($element.parents(".row-fluid:first"));
      return embedded;
    }

    var colspan = $element.parents("form.dynamic-form:first").attr('x-cols') || 4;

    cell = $('<td class="form-item"></td>').attr('colspan', colspan).append(embedded);
    row = $('<tr></tr>').append(cell);
    row.insertAfter($element.parents("tr:first"));

    return embedded;
  };

  $scope.selectMode = "single";

  $scope.findRelativeFields = (function () {
    var relatives = null;
    return function () {
      if (relatives) {
        return relatives;
      }
      var path = $element.attr('x-path');
      relatives = $element.parents().find('[x-field][x-path^="'+path+'."]:not(label,.panel)').map(function() {
        return $(this).attr('x-path').replace(path+'.','');
      }).get();
      relatives.push($scope.field.targetName);
      relatives = _.unique(_.compact(relatives)).filter(function (name) {
        var last = _.last(name.split('.'));
        if (last[0] === '$' || last[0] === '_') {
          return false;
        }
        return true;
      });
      return relatives;
    };
  })();

  $scope.fetchMissingValues = function (value, fields) {
    var nameField = $scope.field.targetName || 'id';
    var related = {};
    var relatives = fields || $scope.findRelativeFields();
    var missing = _.filter(relatives, function (name) {
      return !value || (ui.canSetNested(value, name) && ui.findNested(value, name) === undefined);
    });
    _.each(relatives, function(name) {
      var prefix = name.split('.')[0];
      related[prefix] = value[prefix];
    });
    if (missing.length > 0 && value && value.id) {
      return ds.read(value.id, {
        fields: missing
      }).success(function(rec){
        var record = _.extend({}, related, {
          id: value.id,
          $version: value.version || value.$version
        });
        var trKey = '$t:' + nameField;
        if (trKey in value) {
          record[trKey] = value[trKey];
        }
        if (nameField in rec) {
          record[nameField] = rec[nameField];
        }
        _.each(missing, function(name) {
          var value = ui.findNested(rec, name);
          if (value === undefined) value = null;
          ui.setNested(record, name, value);
        });
        $scope.setValue(record, false);
      });
    }
  };

  $scope.select = function(value) {

    if (_.isArray(value)) {
      value = _.first(value);
    }

    var nameField = $scope.field.targetName || 'id';
    var trKey = '$t:' + nameField;
    var record = value;

    if (value && value.id) {
      record = _.extend({}, {
        id: value.id,
        $version: value.version === undefined ? value.$version : value.version
      });

      var missing = $scope.findRelativeFields().filter(function (name) {
        return !value || ui.findNested(value, name) === undefined;
      });

      if (value[trKey] !== undefined) {
        record[trKey] = value[trKey];
      }

      if (value[nameField] !== undefined) {
        record[nameField] = value[nameField];
      } else {
        missing.push(nameField);
      }

      if (missing.length) {
        return ds.read(value.id, {fields: missing}).success(function(rec) {
          record = _.extend(record, _.omit(rec, 'version'));
          record.$version = rec.version === undefined ? record.$version : rec.version;
          $scope.setValue(record, true);
        });
      }
      if (value.code) {
        record.code = value.code;
      }
    }

    $scope.setValue(record, true);
  };

  $scope.canEditTarget = function () {
    return $scope.canEdit() && $scope.attr('canEdit') !== false;
  };

  $scope.canEdit = function () {
    var parent = $scope.$parent;
    if (parent.canEditTarget && !parent.canEditTarget()) {
      return false;
    }
    // disable edit icon
    if ($scope.attr('canEdit') === undefined) {
      return false;
    }
    return $scope.attr('canEdit') !== false && $scope.canView() && $scope.getValue();
  };

  $scope.canView = function () {
    return $scope.attr('canView') !== false && $scope.getValue();
  };

  $scope.onEdit = function() {
    var record = $scope.getValue();
    $scope.showEditor(record);
  };

  var canNew = $scope.canNew;
  $scope.canNew = function () {
    // disable new icon
    if ($scope.attr('canNew') === undefined) {
      return false;
    }
    return canNew.call($scope);
  };

  $scope._isNestedOpen = false;
  $scope.onSummary = function() {
    $scope._isNestedOpen = !$scope._isNestedOpen;
    var record = $scope.getValue();
    if (record && record.id) {
      return ds.read(record.id).success(function(record){
        $scope.showNestedEditor($scope._isNestedOpen);
      });
    }
    $scope.showNestedEditor($scope._isNestedOpen);
  };

  var icons = null;
  var actions = {
    'new': 'canNew',
    'create': 'canNew',
    'edit': 'canEdit',
    'select': 'canSelect',
    'remove': 'canRemove',
    'clear': 'canRemove'
  };

  $scope.canShowIcon = function (which) {
    var names;
    var field = $scope.field || {};
    var prop = actions[which];
    if (prop !== undefined && $scope.attr(prop) === false) {
      return false;
    }

    if (icons === null) {
      icons = {};
      names = $scope.field.showIcons || $scope.$parent.field.showIcons;
      if (names === false || names === 'false') {
        icons.$all = false;
      } else if (names === true || names === 'true' || names === undefined) {
        icons.$all = true;
      } else if (names) {
        icons.$all = false;
        names = names.split(',');
        names.forEach(function (name) {
          icons[name.trim()] = true;
        });
      }
    }
    return icons.$all || !!icons[which];
  };
}

ui.directive('uiCanSuggest', function () {

  return function (scope, element, attrs) {
    var field = scope.field || {};
    if (field.canSuggest !== false) {
      return;
    }
    element.prop("readonly", true);
    element.addClass("not-readonly");

    // jquery-ui doesn't allow keyboard navigation on autocomplete list
    element.on('keydown', function (e) {
      var inst = element.data('autocomplete');
      switch (e.keyCode) {
      case 38: // up
        inst._keyEvent('previous', e);
        break;
      case 40: // down
        inst._keyEvent('next', e);
        break;
      case  9: // tab
        if (inst.menu.active) {
          inst.menu.select(event);
        }
        break;
      case 13: // enter
        if (inst.menu.active) {
          inst.menu.select(event);
          e.preventDefault();
        }
        break;
      case 27: // escape
        if (inst.menu.element.is(':visible')) {
          inst.close(e);
          e.preventDefault();
        }
        break;
      }
    });
  };
});

var m2oTemplateReadonly = "" +
  "<span class='display-text' ng-show='text && !isHiddenSelf()'>" +
    "<a href='' ng-show='canView()' ng-click='onEdit()'>{{text}}</a>" +
    "<span ng-show='!canView()'>{{text}}</span>" +
  "</span>";

var m2oTemplateEditable = '' +
'<div class="picker-input picker-icons-3 tag-select-single" ng-hide="isHiddenSelf()">'+
'<input type="text" autocomplete="off" ui-can-suggest>'+
'<span class="tag-item label label-info" ng-if="canShowTag" ng-show="text">'+
  '<span class="tag-link tag-text" ng-click="onTagSelect($event)">{{text}}</span> '+
  '<i class="fa fa-times fa-small" ng-click="onTagRemove($event)"></i>'+
'</span>'+
'<span class="picker-icons">'+
  '<i class="fa fa-eye" ng-click="onSummary()" ng-show="hasPermission(\'read\') && _viewParams.summaryView && canToggle()"></i>'+
  '<i class="fa fa-pencil" ng-click="onEdit()" ng-show="hasPermission(\'read\') && canView() && canEdit()" title="{{\'Edit\' | t}}"></i>'+
  '<i class="fa fa-file-text-o" ng-click="onEdit()" ng-show="hasPermission(\'read\') && canView() && !canEdit()" title="{{\'View\' | t}}"></i>'+
  '<i class="fa fa-plus" ng-click="onNew()" ng-show="canNew() && hasPermission(\'write\') && !isDisabled()" title="{{\'New\' | t}}"></i>'+
  '<i class="fa fa-search" ng-click="onSelect()" ng-show="canSelect() && hasPermission(\'read\') && !isDisabled()" title="{{\'Select\' | t}}"></i>'+
'</span>'+
'</div>';

ui.formInput('ManyToOne', 'Select', {

  css	: 'many2one-item',

  widgets: ['OneToOne'],

  controller: ManyToOneCtrl,

  init: function(scope) {
    this._super(scope);

    scope.canShowTag = scope.field['tag-edit'];

    scope.formatItem = function(item) {
      var trKey = "$t:" + scope.field.targetName;
      if (item) {
        return item[trKey] || item[(scope.field.targetName || "id")];
      }
      return "";
    };
  },

  link: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);

    scope.ngModel = model;
    scope.formPath = scope.formPath ? scope.formPath + "." + scope.field.name : scope.field.name;

    var field = scope.field;

    scope.canToggle = function() {
      return (field.widgetAttrs || {}).toggle;
    };

    scope.$evalAsync(function() {
      var relatives = scope.findRelativeFields();
      if (relatives.length > 0) {
        scope.$watch(attrs.ngModel, function m2oValueWatch(value, old) {
          if (value && value.id > 0) {
            scope.fetchMissingValues(scope.getValue(), relatives);
          }
        }, true);
      }
    });

    scope.isHiddenSelf = function() {
      return scope.isHidden();
    };

    if (field.widget === 'nested-editor') {

      scope.isHiddenSelf = function() {
        if (!scope.canSelect()) {
          return true;
        }
        if (scope.canToggle() === 'both' && scope._isNestedOpen) {
          return true;
        }
        return scope.isHidden();
      };

      var hiddenSet = false;
      var hasHidden = false;

      scope.$watch('attr("hidden")', function m2oHiddenWatch(hidden, old) {
        if (hidden && !hiddenSet) hasHidden = true;
        if (hiddenSet && hidden === old) return;
        hiddenSet = true;
        if (scope._isNestedOpen || hasHidden) {
          scope.showNestedEditor(!hidden);
        }
      });
      scope.$on("on:check-nested-values", function (e, value) {
        if (scope.isHidden() && value) {
          var val = scope.getValue() || {};
          if (val.$updatedValues === value && val.id === value.id) {
            _.extend(val, value);
          }
        }
      });

      scope.$timeout(function() {
        if (!scope.attr("hidden")) {
          scope.onSummary();
        }
      });
    }
  },

  link_editable: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);
    var input = this.findInput(element);
    var field = scope.field;

    function create(term, popup) {
      scope.createOnTheFly(term, popup, function (record) {
        scope.select(record);
      });
    }

    input.on("change", function () {
      input.attr('placeholder', input.is(':focus') ? _t('Search...') : null);
    });
    input.on("focus", function () {
      // XXX: firefox prevents click event, bug in FF?
      if (!axelor.browser.mozilla) {
        input.attr('placeholder', _t('Search...'));
      }
    });
    input.on("blur", function () {
      input.attr('placeholder', field.placeholder || '');
    });

    var validateSearch = _.debounce(function () {
      if (input.length === 0 || input.is(':hidden')) return;
      var my = String(input.val() || '');
      var text = String(scope.text || '').replace(/\n/g, '');
      var invalid = my !== text;
      scope.$applyAsync(function () {
        scope.setValidity('search', !invalid);
      });
    });

    if (input.length) {
      input.on("input", validateSearch);
      input.on("keydown", validateSearch);
      input.on("propertychange", validateSearch);
      scope.$on("on:edit", validateSearch);
      scope.$on("on:new", validateSearch);
      scope.$watch("text", validateSearch);
    }

    scope.loadSelection = function(request, response) {

      if (!scope.canSelect()) {
        return response([]);
      }

      this.fetchSelection(request, function(items, page) {
        var term = request.term;

        if (scope.canSelect() && (items.length < page.total || (request.term && items.length === 0))) {
          if (items.length === 0) {
            items.push({
              label: _t("No results found"),
              click: function() {}
            });
          } else {
            items.push({
              label: _t("Search more..."),
              click: function() { scope.showSelector(); }
            });
          }
        }

        if (field.create && term && scope.canNew()) {
          items.push({
            label : _t('Create "{0}" and select...', term),
            click : function() { create(term); }
          });
          items.push({
            label : _t('Create "{0}"...', term),
            click : function() { create(term, true); }
          });
        }

        if ((field.create === undefined || (field.create && !term)) && scope.canNew()) {
          items.push({
            label: _t("Create..."),
            click: function() { scope.showPopupEditor(); }
          });
        }

        response(items, page);
      });
    };

    scope.matchValues = function(a, b) {
      if (a === b) return true;
      if (!a) return false;
      if (!b) return false;
      return a.id === b.id;
    };

    scope.setValidity = function(key, value) {
      model.$setValidity(key, value);
    };

    if ((scope._viewParams || {}).summaryView) {
      element.removeClass('picker-icons-3').addClass('picker-icons-4');
    }

    scope.isDisabled = function() {
      return this.isReadonly();
    };

    input.keydown(function(e){
      var handled = false;
      if (e.keyCode == 113) { // F2
        if (e.shiftKey || !scope.getValue()) {
          if (scope.canNew()) {
            scope.onNew();
          }
        } else {
          if (scope.canEdit()) {
            scope.onEdit();
          }
        }
        handled = true;
      }
      if (e.keyCode == 114) { // F3
        if (scope.canSelect()) {
          scope.onSelect();
        }
        handled = true;
      }
      if (!handled) {
        return;
      }
      e.preventDefault();
      e.stopPropagation();
      return false;
    });

    scope.handleEnter = function (e) {
      var handled = false;
      var widget = input.autocomplete('widget');
      if (widget) {
        var item = widget.find('li .ui-state-focus').parent();
        if (item.length === 0) {
          item = widget.find('li:not(.tag-select-action)');
          item = item.length === 1 ? item.first() : null;
        }
        var data = item ? item.data('ui-autocomplete-item') : null;
        if (data) {
          input.autocomplete('close');
          if (data.click) {
            data.click.call(scope);
          } else {
            scope.setValidity('search', true);
            scope.select(data.value);
            handled = true;
          }
        }
        if (scope.onChangeNotify && handled) {
          scope.notifyChangeNeeded = true;
        } else {
          scope.$timeout(function() {
            adjustPadding();
          }, 100);
        }
      }
    };

    scope.handleSelect = function(e, ui) {
      var handled = false;
      if (ui.item.click) {
        ui.item.click.call(scope);
      } else {
        scope.setValidity('search', true);
        scope.select(ui.item.value);
        handled = true;
      }
      if (scope.onChangeNotify && handled) {
        scope.notifyChangeNeeded = true;
      } else {
        scope.$timeout(function() {
          adjustPadding();
        }, 100);
      }
    };

    scope.$on("on:record-change", function() {
      if (scope.notifyChangeNeeded) {
        scope.notifyChangeNeeded = false;
        scope.$timeout(function() {
          adjustPadding();
          scope.onChangeNotify(scope, scope.record);
        }, 100);
      }
    });

    scope.$render_editable = function() {

      if (scope.canShowTag) {
        return setTimeout(adjustPadding, 100);
      }

      var value = scope.getValue();
      var name = scope.field.targetName;
      if (value && value.id > 0 && !value[name]) {
        return scope._dataSource.details(value.id, name).success(function(rec) {
          value[name] = rec[name];
          input.val(scope.getText());
          validateSearch();
        });
      }

      input.val(scope.getText());
      validateSearch();
    };

    if (scope.field && scope.field['tag-edit']) {
      scope.attachTagEditor(scope, element, attrs);
    }

    scope.onTagSelect = function (e) {
      if (scope.canEdit()) {
        return scope.onTagEdit(e, scope.getValue());
      }
      scope.onEdit();
    };

    scope.onTagRemove = function (e) {
      scope.setValue(null);
    };

    function adjustPadding() {
      var tag = element.find('span.tag-link');
      if (tag.length && tag.is(':visible')) {
        input.css('padding-left', tag.width() + 24);
      } else {
        input.css('padding-left', '');
      }
      if (scope.canShowTag) {
        input.val('');
      }
    }

    if (scope.canShowTag) {
      input.focus(adjustPadding);
    }
  },
  template_editable: m2oTemplateEditable,
  template_readonly: m2oTemplateReadonly
});

ui.InlineManyToOneCtrl = InlineManyToOneCtrl;
ui.InlineManyToOneCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];

function InlineManyToOneCtrl($scope, $element, DataSource, ViewService) {

  var field = $scope.field || $scope.getViewDef($element);
  var names = [];
  var params = {
    model: field.target
  };

  if (field.editor) {
    params.views = [{
      type: 'form',
      items: field.editor.items
    }];
    names = _.pluck(field.editor.items, 'name');
    names = _.compact(names);
  } else if (field.viewer && field.viewer.depends) {
    names = field.viewer.depends.split(/\s*,\s*/);
  }

  $scope._viewParams = params;

  ManyToOneCtrl.call(this, $scope, $element, DataSource, ViewService);

  if (field.targetName) {
    names.push(field.targetName);
  }

  names = _.unique(names);

  $scope.select = function (value) {
    if (_.isArray(value)) {
      value = _.first(value);
    }
    if (_.isEmpty(value)) {
      return $scope.setValue(null, true);
    }

    function set(val) {
      var record = _.pick(val, names);
      record.id = val.id;
      record.$version = val.version || val.$version;
      $scope.setValue(record, true);
    }

    var record = _.pick(value, names);

    // if some field is missing
    if (_.keys(record).length !== names.length && value.id) {
      return $scope._dataSource.read(value.id, {
        fields: names
      }).success(function (rec) {
        set(rec);
      });
    }
    set(value);
  };

  $scope.onClear = function() {
    $scope.setValue(null, true);
  };
}

ui.formInput('InlineManyToOne', 'ManyToOne', {

  widgets: ['InlineOneToOne'],

  controller: InlineManyToOneCtrl,

  template_readonly: function (scope) {
    var field = scope.field || {};
    if (field.viewer) {
      return field.viewer.template;
    }
    if (field.editor && (field.editor.viewer || !field.targetName)) {
      return null;
    }
    return m2oTemplateReadonly;
  },

  template_editable: function (scope) {
    var editor = scope.field.editor || {};
    var template = "" +
    "<div class='m2o-editor'>" +
      "<div class='m2o-editor-controls' ng-show='!isReadonly()'>" +
        "<a href='' ng-show='canEdit() && canShowIcon(\"edit\")' ng-click='onEdit()' title='{{\"Edit\" | t}}'><i class='fa fa-pencil'></i></a>" +
        "<a href='' ng-show='!canEdit() && canView() && canShowIcon(\"view\")' ng-click='onEdit()' title='{{\"View\" | t}}'><i class='fa fa-file-text-o'></i></a>" +
        "<a href='' ng-show='canSelect() && canShowIcon(\"select\")' ng-click='onSelect()' title='{{\"Select\" | t}}'><i class='fa fa-search'></i></a>" +
        "<a href='' ng-show='canShowIcon(\"clear\")' ng-click='onClear()' title='{{\"Clear\" | t}}'><i class='fa fa-times-circle'></i></a>" +
      "</div>" +
      "<div class='m2o-editor-form' ui-panel-editor></div>" +
    "</div>";
    if (editor.showOnNew !== false) {
      return template;
    }
    scope.canShowEditor = function () {
      return (scope.record || {}).id > 0 || !!scope.getValue();
    };
    template =
      "<div class='m2o-editor-switcher'>" +
      "<div ng-show='!canShowEditor()' class='form-item-container'>"+ m2oTemplateEditable +"</div>" +
      "<div ng-show='canShowEditor()'>"+ template +"</div>" +
      "</div>";
    return template;
  },

  template: null
});

ui.formInput('SuggestBox', 'ManyToOne', {
  metaWidget: true,
  link_editable: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);
    var field = scope.field;
    if (field.canNew === undefined && field.create) {
      field.canNew = true;
    }
    if (field.create === undefined && field.canNew) {
      field.create = true;
    }
  },
  template_editable:
  '<span class="picker-input picker-icons-2">'+
    '<input type="text" autocomplete="off" ui-can-suggest>'+
    '<span class="picker-icons picker-icons-2">'+
      '<i class="fa fa-pencil" ng-click="onEdit()" ng-show="hasPermission(\'read\') && canView() && canEdit()" title="{{\'Edit\' | t}}"></i>'+
      '<i class="fa fa-caret-down" ng-click="showSelection()"></i>'+
    '</span>'+
   '</span>'
});

ui.formInput('RefSelect', {

  css: 'multi-object-select',

  metaWidget: true,

  controller: ['$scope', 'ViewService', function($scope, ViewService) {

    $scope.createElement = function(id, name, selectionList, related) {

      var elemGroup = $('<div ui-group ui-table-layout cols="2" x-widths="150,*"></div>');
      var elemSelect = $('<input ui-select showTitle="false">')
        .attr("name", name)
        .attr("x-for-widget", id)
        .attr("ng-model", "record." + name);

      var elemSelects = $('<div></div>').attr('ng-switch', "record." + name);
      var elemItems = _.map(selectionList, function(s) {
        return $('<input ui-ref-item ng-switch-when="' + s.value +'">')
          .attr('ng-model', 'record.$_' + related)
          .attr('x-target', s.value)
          .attr('x-watch-name', name)
          .attr('x-ref', related);
      });

      elemGroup
        .append($('<div class="multi-object-select-first" ng-show="!isLink || (isLink && !isReadonly())"></div>').append(elemSelect))
        .append(elemSelects.append(elemItems));

      return ViewService.compile(elemGroup)($scope);
    };
  }],

  link: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);

    var name = scope.field.name,
      selectionList = scope.field.selectionList,
      related = scope.field.related || scope.field.name + "Id";

    scope.isLink = this.isLink;
    scope.fieldsCache = {};
    scope.selectionList = selectionList;

    scope.refFireEvent = function (name) {
      var handler = scope.$events[name];
      if (handler) {
        return handler();
      }
    };

    var elem = scope.createElement(element.attr('id'), name, selectionList, related);
    setTimeout(function() {
      element.append(elem);
    });

    setTimeout(function () {
      var selectScope = element.find('[name="'+name+'"]').scope();
      var setValue = selectScope.setValue;
      selectScope.setValue = function (value) {
        var old = scope.getValue();
        if (old !== value && scope.record) {
          scope.record[related] = null;
        }
        setValue.apply(selectScope, arguments);
      };
    });
  },
  template_editable: null,
  template_readonly: null
});

ui.formInput('RefLink', 'RefSelect', {
  css: 'multi-object-select multi-object-link',
  isLink: true
});

ui.formInput('RefItem', 'ManyToOne', {

  showTitle: false,

  controller: ['$scope', '$element', 'DataSource', 'ViewService', function ($scope, $element, DataSource, ViewService) {

    var target = $element.attr('x-target');
    var data = (_.findWhere($scope.$parent.selectionList || $scope.$parent.field.selectionList, { value: target})||{}).data || {};

    var getViewDef = $scope.getViewDef;
    $scope.getViewDef = function (elem) {
      if (elem === $element) {
        return _.extend({}, {
          domain: data.domain,
          formView: data.form,
          gridView: data.grid
        });
      }
      return getViewDef.call($scope, elem);
    };

    ManyToOneCtrl.apply(this, arguments);
  }],

  link: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);

    if (scope.field.targetName) {
      return this._link.apply(this, arguments);
    }

    var self = this;
    var target = element.attr('x-target');
    var data = (_.findWhere(scope.$parent.selectionList, {value: target})||{}).data || {};

    function doLink(fields) {
      var name = false,
        search = [];

      _.each(fields, function(f) {
        if (f.nameColumn) name = f.name;
        if (f.name === "name") search.push("name");
        if (f.name === "code") search.push("code");
      });

      if (!name && _.contains(search, "name")) {
        name = "name";
      }

      _.extend(scope.field, {
        target: scope._model,
        targetName: name,
        targetSearch: search,
        domain: data.domain,
        canEdit: false
      });

      self._link(scope, element, attrs, model);
      scope.setDomain(data.domain, data.context);

      if (scope.$parent.isLink) {
        scope.onEdit = function () {
          var value = scope.getValue() || {};
          scope.openTab({
            action: _.uniqueId('$act'),
            model: scope._model,
            viewType: "form",
            views: [{ type: "form" }]
          }, {
            mode: "edit",
            state: value.id
          });
        };
      }
    }

    if (scope.fieldsCache[scope._model]) {
      doLink(scope.fieldsCache[scope._model]);
    } else {
      scope.loadFields().success(function (fields) {
        scope.fieldsCache[scope._model] = fields;
        doLink(fields);
      });
    }
  },

  _link: function(scope, element, attrs, model) {

    var ref = element.attr('x-ref');
    var watch = element.attr('x-watch-name');
    var target = element.attr('x-target');
    var refField = scope.fields[ref] || {};

    function setRef(value) {
      if (!scope.record) {
        return;
      }

      if (value && refField.type === 'string') {
        value = '' + value;
      }

      var old = scope.record[ref];
      scope.record[ref] = value;

      if (old != value) {
        scope.refFireEvent('onChange');
      }
    }

    scope.targetValue = target;

    var __setValue = scope.setValue;
    scope.setValue = function(value) {
      __setValue.call(scope, value);
      setRef(value ? value.id : 0);
    };

    var doSelect = _.debounce(function () {
      scope.$timeout(function() {
        var ds = scope._dataSource;
        var value = (scope.record || {})[ref];
        if (!value) return;
        var v = scope.getValue();
        if (v && v.id === value) return;
        var nameField = scope.field.targetName;
        var fields = nameField ? [nameField] : undefined;
        var promise = ds._request('fetch', value).post({ fields: fields }, { silent: true });
        promise.success(function (response) {
          var record = _.first(response.data) || (value ? { id: value } : null);
          scope.select(record);
        }).error(function () {
          var record = value ? { id: value } : null;
          if (record) {
            record[nameField] = value;
          }
          scope.setValue(record, false);
        });
      });
    }, 100);

    var watchExpr = "record.id + record." + watch + " + record." + ref;
    scope.$watch(watchExpr, doSelect);
    scope.$watch("record", doSelect);
  }
});

ui.formInput('RefText', 'ManyToOne', {
  metaWidget: true,
  link_editable: function (scope, element, attrs) {
    this._super.apply(this, arguments);

    if (!scope.field.create) {
      return;
    }

    function freeSelect(text) {
      return function () {
        scope.$timeout(function () {
          scope.select(text);
        });
      };
    }

    scope.loadSelection = function(request, response) {
      this.fetchSelection(request, function(items, page) {
        var term = request.term;
        if (term) {
          items.push({
            label : _t('Select "{0}"...', term),
            click : freeSelect(term)
          });
        }
        response(items);
      });
    };
  },
  link: function (scope, element, attrs, model) {
    scope.canNew = function () { return false; };
    scope.canView = function () { return false; };
    scope.canEdit = function () { return false; };

    var field = scope.field;
    var targetName = field.targetName || "name";

    scope._viewParams.views = [{
      type: "grid",
      items: [{ name: targetName, type: "field" }]
    }];

    scope.formatItem = function (record) {
      return record || "";
    };

    scope.select = function (value) {
      var record = _.isArray(value) ? _.first(value) : value;
      var val = _.isString(record) ? record : (record || {})[targetName];
      if (val === undefined) {
        val = null;
      }
      scope.setValue(val, true);
    };
  }
});

ui.formInput('EvalRefSelect', 'Select', {

  controller: ['$scope', '$element', 'DataSource', 'ViewService', function ($scope, $element, DataSource, ViewService) {

    var fetchDS;

    $scope.$fetchDS = function () {
      var target = this.$target;
      if (!fetchDS || fetchDS._model !== target) {
        fetchDS = DataSource.create(target);
      }
      return fetchDS;
    };

    $scope.canNew = function () {
      return false;
    };

    $scope.showSelector = function () {
      var child = $scope.$new();
      child._viewParams = {
        model: $scope.$target,
        views: [{
          type: 'grid',
          items: [{
            type: 'field',
            name: $scope.$targetName
          }]
        }]
      };

      var selector = ViewService.compile('<div ui-selector-popup x-select-mode="single"></div>')(child);
      var popup = selector.isolateScope();

      selector.on('dialogclose', function () {
        selector.remove();
        child.$destroy();
      });

      popup.show();
    };
  }],

  init: function(scope) {
    this._super.apply(this, arguments);

    var field = scope.field;

    function toValue(value) {
      var val = value;
      if (val === undefined || val === null) {
        val = null;
      } else {
        val = '"' + val + '"';
      }
      return val;
    }

    Object.defineProperties(scope, {

      $target: {
        get: function () {
          return scope.$eval(field.evalTarget);
        }
      },

      $targetName: {
        get: function () {
          return scope.$eval(field.evalTargetName);
        }
      },

      $recordValue: {
        get: function () {
          return scope.$eval(field.evalValue);
        },
        set: function (value) {
          scope.$eval(field.evalValue + " = " + toValue(value));
        }
      },

      $recordTitle: {
        get: function () {
          return scope.$eval(field.evalTitle);
        },
        set: function (value) {
          scope.$eval(field.evalTitle + " = " + toValue(value));
        }
      }
    });

    scope.formatItem = function (value) {
      return scope.$recordTitle;
    };

    scope.select = function (value) {
      var item = _.isArray(value) ? _.first(value) : value;
      scope.setValue(item);
    };

    scope.setValue = function (value) {
      var nameField = scope.$targetName;
      var val = value || {};
      scope.$recordValue = val.id;
      scope.$recordTitle = val[nameField];
    };
  },

  link_editable: function(scope, element, attrs, model) {
    this._super.apply(this, arguments);

    scope.handleSelect = function(e, ui) {
      if (ui.item.click) {
        ui.item.click.call(scope);
      } else {
        scope.select(ui.item.value);
      }
      scope.$applyAsync();
    };

    scope.loadSelection = function(request, response) {
      var targetName = scope.$targetName;
      if (!targetName) {
        return response([]);
      }

      var ds = scope.$fetchDS();
      var filter = {};

      if (request.term) {
        filter[targetName] = request.term;
      }

      ds.search({
        fields: ['id', targetName],
        filter: filter,
        limit: axelor.device.small ? 6 : 10
      }).success(function (records, page) {
        var items = _.map(records, function(record) {
          return {
            label: record[targetName],
            value: record
          };
        });

        if (items.length < page.total || (request.term && items.length === 0)) {
          items.push({
            label: _t("Search more..."),
            click: function() {
              scope.showSelector();
            }
          });
        }

        response(items, page);
      });
    };
  }
});

})();
